﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics.CodeAnalysis;

namespace DomainExample.Repository
{
    /// <summary>
    /// The BaseRepository is responsible for the connection and should be the only
    /// class with knowledge of the concrete connection type and command. The repositories that
    /// inherit from BaseRepository should only know about the general IDbCommand.
    /// </summary>
    public class BaseRepository : IDisposable
    {
        private readonly IDataProvider _dataProvider;

        public BaseRepository(IDataProvider dataProvider)
        {
            _dataProvider = dataProvider;
        }

        protected IDbCommand NewCommand(string query)
        {
            return _dataProvider.NewCommand(query);
        }

        ~BaseRepository()
        {
            Dispose(false);
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected void AddParameter(IDbCommand command, string name, object value)
        {
            command.Parameters.Add(_dataProvider.NewParameter(name, value));
        }

        protected IEnumerable<T> GetEntitiesFromDatabase<T>(IDbCommand command)
        {
            IList<T> results = new List<T>();

            try
            {
                command.Connection.Open();
                using(var reader = command.ExecuteReader(CommandBehavior.Default))
                {
                    while (reader.Read())
                    {
                        results.Add((T)MapRowToEntity(reader));
                    }
                }
            }
            finally
            {
                CloseConnection(command);
            }
            
            return results;
        }

        protected void ExecuteNonQuery(IDbCommand command)
        {
            try
            {
                command.Connection.Open();
                command.ExecuteNonQuery();
            }
            finally
            {
                CloseConnection(command);
            }
        }

        protected void ExecuteInsert(IDbCommand command, out Int32 id)
        {
            try
            {
                var result = ExecuteInsert(command);
                id = Convert.ToInt32(result);
            }
            finally
            {
                CloseConnection(command);
            }
        }

        protected void ExecuteInsert(IDbCommand command, out Int64 id)
        {
            try
            {
                var result = ExecuteInsert(command);
                id = Convert.ToInt64(result);
            }
            finally
            {
                CloseConnection(command);
            }
        }

        [ExcludeFromCodeCoverage]
        protected virtual object MapRowToEntity(IDataReader reader)
        {
            throw new InvalidOperationException("MapRowToEntity must be overridden in the specialised repository.");
        }

        private static void CloseConnection(IDbCommand command)
        {
            if (command.Connection != null && command.Connection.State != ConnectionState.Closed)
            {
                command.Connection.Close();
            }
        }

        private object ExecuteInsert(IDbCommand command)
        {
            command.Connection.Open();
            command.ExecuteNonQuery();

            command.Parameters.Clear();
            command.CommandText = "SELECT @@IDENTITY";
            var result = command.ExecuteScalar();
            return result;
        }

        private void Dispose(bool disposing)
        {
            if (disposing)
            {
                _dataProvider.Dispose();
            }
        }
    }
}
